package run.iget.webcoket.handler;

import org.tio.core.ChannelContext;
import org.tio.core.Tio;
import org.tio.http.common.HttpRequest;
import org.tio.http.common.HttpResponse;
import org.tio.websocket.common.WsRequest;
import org.tio.websocket.server.handler.IWsMsgHandler;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import run.iget.framework.event.EventPublishUtils;
import run.iget.security.util.LoginUtils;
import run.iget.webcoket.bean.WSClientMsg;
import run.iget.webcoket.constant.WSConst;
import run.iget.webcoket.enums.WsMethod;

/**
 * 代码千万行，注释第一行，注释不规范，迭代两行泪
 * ---------------类描述-----------------
 * WebSocket消息处理器
 * ---------------类描述-----------------
 *
 * @author 大周
 * @date 2023/1/16 15:04
 */
@Slf4j
public class WsMsgHandler implements IWsMsgHandler {

    /**
     * <li>对httpResponse参数进行补充并返回，如果返回null表示不想和对方建立连接，框架会断开连接，如果返回非null，框架会把这个对象发送给对方</li>
     * <li>注：请不要在这个方法中向对方发送任何消息，因为这个时候握手还没完成，发消息会导致协议交互失败。</li>
     * <li>对于大部分业务，该方法只需要一行代码：return httpResponse;</li>
     *
     * @param httpRequest
     * @param httpResponse
     * @param channelContext
     * @return
     * @throws Exception
     */
    @Override
    public HttpResponse handshake(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext)
            throws Exception {
        String id = httpRequest.getParam(WSConst.CONNECT_REQ_USER_ID);
        if (!isAllowed(id)) {
            // 如果没有登录，则不建立链接
            return null;
        }
        return httpResponse;
    }

    /**
     * 握手成功后触发该方法
     *
     * @param httpRequest
     * @param httpResponse
     * @param channelContext
     * @throws Exception
     */
    @Override
    public void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext)
            throws Exception {
        // 拿到用户id
        String id = httpRequest.getParam(WSConst.CONNECT_REQ_USER_ID);
        // 绑定用户
        Tio.bindUser(channelContext, id);

        // 给用户发送消息
        //        Map<String, String> msg = new HashMap<>(2);
        //        msg.put(WSConst.CONNECT_REQ_USER_ID, id);
        //        msg.put("dataTime", DateUtil.now());
        //
        //        WSUtils.send(id, JSON.toJSONString(ResultResp.ok(msg)));

        // 发送事件，方便扩展处理
        WSClientMsg wsClientMsg = new WSClientMsg(channelContext, WsMethod.ON_CONNECT,
                httpRequest.getParam(WSConst.CONNECT_REQ_PARAM));
        // 发送事件
        EventPublishUtils.publish(wsClientMsg);
    }

    @Override
    public Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
        return null;
    }

    /**
     * 当收到Opcode.CLOSE时，执行该方法，业务层在该方法中一般不需要写什么逻辑，空着就好
     *
     * @param wsRequest
     * @param bytes
     * @param channelContext
     * @return
     * @throws Exception
     */
    @Override
    public Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
        // 拿到用户id
        String idAndIp = this.getClientIdAndIp(channelContext);
        // 构建消息
        WSClientMsg wsClientMsg = new WSClientMsg(channelContext, WsMethod.ON_CLOSE, null);
        // 解除绑定
        Tio.unbindUser(channelContext);
        // 关闭连接
        Tio.remove(channelContext, "WebSocket Close：" + idAndIp);
        // 发送事件，方便扩展处理
        EventPublishUtils.publish(wsClientMsg);
        return null;
    }

    /**
     * 当收到Opcode.TEXT消息时，执行该方法。也就是说如何你的ws是基于TEXT传输的，就会走到这个方法
     *
     * @param wsRequest
     * @param msg
     * @param channelContext
     * @return
     * @throws Exception
     */
    @Override
    public Object onText(WsRequest wsRequest, String msg, ChannelContext channelContext) throws Exception {
        if (WSConst.SHOW_MSG) {
            String idAndIp = this.getClientIdAndIp(channelContext);
            log.info("服务器已收到消息：{}-{}", idAndIp, msg);
        }
        // 发送事件，方便扩展处理
        EventPublishUtils.publish(new WSClientMsg(channelContext, WsMethod.ON_TEXT, msg));
        return null;
    }

    private boolean isAllowed(String id) {
        if (StrUtil.isBlank(id)) {
            return false;
        }
        return LoginUtils.isLogin(Long.parseLong(id));
    }

    private String getClientId(ChannelContext channelContext) {
        String userId = channelContext.userid;
        if (StrUtil.isBlank(userId)) {
            return channelContext.getId();
        }
        return userId;
    }

    /**
     * 获取客户端的id和ip地址
     *
     * @param channelContext
     * @return
     */
    private String getClientIdAndIp(ChannelContext channelContext) {
        String userId = this.getClientId(channelContext);
        return userId + "-" + channelContext.getClientNode().getIp();
    }

}
